# -*- coding: utf-8 -*-
# @Author  : pony.guan
# @Time    : 2022/3/7 13:58
# Description:

import uuid
import json
import random
import time
import requests
from tenacity import retry, stop_after_delay, stop_after_attempt, wait_fixed, retry_if_result


class EsCycle:

    def __init__(self, ip='172.20.110.51', port=19000):
        self.ip = ip
        self.port = port
        self.move_location_url = f'http://{ip}:{port}/wms/createWmsRelocationTask'
        self.location_msg_url = f'http://{ip}/ess-api/model/queryModelByType?modelType=location'
        self.container_msg_url = f'http://{ip}/ess-api/model/queryModelByType?modelType=container'

        self.headers = {
            "Content-Type": "application/json"
        }

        self.container_in_2nd_floor, \
        self.container_in_5th_floor, \
        self.location_with_container \
            = self.get_container_msg() if self.ip != '172.20.110.51' else self.get_container_msg1()

        self.location_without_container_in_2nd_floor, \
        self.location_without_container_in_5th_floor \
            = self.get_location_msg() if self.ip != '172.20.110.51' else self.get_location_msg1()

    def update_contaier_location_msg(self):
        self.container_in_2nd_floor, \
        self.container_in_5th_floor, \
        self.location_with_container \
            = self.get_container_msg() if self.ip != '172.20.110.51' else self.get_container_msg1()
        self.location_without_container_in_2nd_floor, \
        self.location_without_container_in_5th_floor \
            = self.get_location_msg() if self.ip != '172.20.110.51' else self.get_location_msg1()

    def get_container_msg(self):

        # 2楼库位内的容器, 5楼库位内的容器, 被容器占据了的库位
        ret = requests.get(url=self.container_msg_url, headers=self.headers).json()
        container_lst = ret['data']['container']

        container_in_2nd_floor = []  # 2楼库位内的容器
        container_in_5th_floor = []  # 5楼库位内的容器

        location_with_container = []  # 被容器占据了的库位

        for container in container_lst:
            code = container['code']
            positionCode = container['positionCode']

            if positionCode:  # 在库内的容器 (库位有容器)
                location_with_container.append(positionCode)
                if positionCode.startswith('HAI'):  # 以HAI开头
                    # if 1:
                    if positionCode.endswith('@2'):
                        # if 1:
                        container_in_2nd_floor.append({
                            'code': code,
                            'positionCode': positionCode
                        })

                    elif positionCode.endswith('@5'):
                        # if 1:
                        container_in_5th_floor.append({
                            'code': code,
                            'positionCode': positionCode
                        })
        return container_in_2nd_floor, container_in_5th_floor, location_with_container

    def get_container_msg1(self):

        # 2楼库位内的容器, 5楼库位内的容器, 被容器占据了的库位
        ret = requests.get(url=self.container_msg_url, headers=self.headers).json()
        container_lst = ret['data']['container']

        container_in_2nd_floor = []  # 2楼库位内的容器
        container_in_5th_floor = []  # 5楼库位内的容器

        location_with_container = []  # 被容器占据了的库位

        for container in container_lst:
            code = container['code']
            positionCode = container['positionCode']

            if positionCode:  # 在库内的容器 (库位有容器)
                location_with_container.append(positionCode)
                if positionCode.startswith('HAI'):  # 以HAI开头
                    # if 1:
                    # if positionCode.endswith('@2'):
                    if 1:
                        container_in_2nd_floor.append({
                            'code': code,
                            'positionCode': positionCode
                        })

                    # elif positionCode.endswith('@5'):
                    if 1:
                        container_in_5th_floor.append({
                            'code': code,
                            'positionCode': positionCode
                        })
        return container_in_2nd_floor[:len(container_in_2nd_floor) // 2], \
               container_in_2nd_floor[len(container_in_2nd_floor) // 2:], \
               location_with_container

    def get_location_msg(self):
        ret = requests.get(url=self.location_msg_url, headers=self.headers).json()
        location_code_lst = list(ret['data']['modelUpdated'].keys())

        location_without_container_in_2nd_floor = []  # 2楼不带容器的库位
        location_without_container_in_5th_floor = []  # 5楼不带容器的库位

        for location_code in location_code_lst:

            if location_code.startswith('HAI') and location_code not in self.location_with_container:  # 以HAI开头的空库位
                # if 1 and location_code not in self.location_with_container:
                if location_code.endswith('@2'):
                    # if 1:
                    location_without_container_in_2nd_floor.append(location_code)

                elif location_code.endswith('@5'):
                    # if 1:
                    location_without_container_in_5th_floor.append(location_code)

        return location_without_container_in_2nd_floor, location_without_container_in_5th_floor

    def get_location_msg1(self):
        ret = requests.get(url=self.location_msg_url, headers=self.headers).json()
        location_code_lst = list(ret['data']['modelUpdated'].keys())

        location_without_container_in_2nd_floor = []  # 2楼不带容器的库位
        location_without_container_in_5th_floor = []  # 5楼不带容器的库位

        for location_code in location_code_lst:

            if location_code.startswith('HAI') and location_code not in self.location_with_container:  # 以HAI开头的空库位
                # if 1 and location_code not in self.location_with_container:
                # if location_code.endswith('@2'):
                if 1:
                    location_without_container_in_2nd_floor.append(location_code)

                # elif location_code.endswith('@5'):
                if 1:
                    location_without_container_in_5th_floor.append(location_code)

        return location_without_container_in_2nd_floor[
               : len(location_without_container_in_2nd_floor) // 2], location_without_container_in_2nd_floor[
                                                                     len(location_without_container_in_2nd_floor) // 2:]

    def move_task(self, container_code2location_code):
        tasks = []
        for container_code, to_location_code in container_code2location_code.items():
            tasks.append({
                "taskTemplateCode": "TOTE_RELOCATION",
                "taskCode": str(uuid.uuid4()),  # 唯一
                "taskPriority": 0,
                "taskDescribe": {
                    "containerCode": container_code,  #
                    "fromLocationCode": "",
                    "toLocationCode": to_location_code  #
                }
            })

        data = {
            "taskGroupCode": "",
            "groupPriority": 0,
            "tasks": tasks
        }

        r = requests.post(url=self.move_location_url, data=json.dumps(data), headers=self.headers)
        if int(r.json()['code']) in (0, 200):
            return True
        else:
            return False

    @retry(stop=(stop_after_delay(20 * 60) | stop_after_attempt(1000000)), wait=wait_fixed(10),
           retry=retry_if_result(lambda value: value is None), reraise=True)
    def check_task(self, container_code2location_code):
        """
        检查移库任务是否执行成功
        :return:
        """
        ret = requests.get(url=self.container_msg_url, headers=self.headers).json()
        container_lst = ret['data']['container']
        succeed_count = 0
        for container_code, to_location_code in container_code2location_code.items():
            for container in container_lst:
                code = container['code']
                if code == container_code:
                    positionCode = container['positionCode']
                    if positionCode == to_location_code:
                        succeed_count += 1
        if succeed_count == len(container_code2location_code):
            return True

    def cycle_move(self, cycle_count, floor2count):
        """
        @循环移库入口
        :param cycle_count:
        :param floor2count:
        :return:
        """

        count_of_2nd_floor, count_of_5th_floor = floor2count['2nd_floor'], floor2count['5th_floor']

        if count_of_2nd_floor + count_of_5th_floor < 1:
            return

        if len(self.container_in_2nd_floor) < count_of_2nd_floor:
            raise ValueError('容器数量太大，2楼库内没有这么多容器')
        if len(self.location_without_container_in_2nd_floor) < count_of_2nd_floor:
            raise ValueError('容器数量太大，2楼库内没有这么多空库位')

        if len(self.container_in_5th_floor) < count_of_5th_floor:
            raise ValueError('容器数量太大，5楼库内没有这么多容器')
        if len(self.location_without_container_in_5th_floor) < count_of_5th_floor:
            raise ValueError('容器数量太大，5楼库内没有这么多空库位')

        print('开始执行循环移库')

        container_position_2nd_lst = random.sample(self.container_in_2nd_floor, count_of_2nd_floor)
        container_position_5th_lst = random.sample(self.container_in_5th_floor, count_of_5th_floor)

        container_code_2nd_lst = [container_position['code'] for container_position in container_position_2nd_lst]
        container_code_5th_lst = [container_position['code'] for container_position in container_position_5th_lst]

        for _ in range(cycle_count):
            self.update_contaier_location_msg()
            to_location_code_2nd_lst = random.sample(self.location_without_container_in_2nd_floor, count_of_2nd_floor)
            to_location_code_5th_lst = random.sample(self.location_without_container_in_5th_floor, count_of_5th_floor)
            container_code2location_code_2nd = dict(zip(container_code_2nd_lst, to_location_code_2nd_lst))
            container_code2location_code_5th = dict(zip(container_code_5th_lst, to_location_code_5th_lst))

            container_code2location_code = {**container_code2location_code_2nd, **container_code2location_code_5th}

            ret = self.move_task(container_code2location_code)

            if ret:
                print('\n')
                for container_code_2nd, to_location_code_2nd in container_code2location_code_2nd.items():
                    print(f'2楼移库任务下发成功: {container_code_2nd}--->{to_location_code_2nd}')

                print('\n')
                for container_code_5th, to_location_code_5th in container_code2location_code_5th.items():
                    print(f'5楼移库任务下发成功: {container_code_5th}--->{to_location_code_5th}')

            else:
                print('\n')
                for container_code_2nd, to_location_code_2nd in container_code2location_code_2nd.items():
                    print(f'2楼移库任务下发失败: {container_code_2nd}--->{to_location_code_2nd}')
                print('\n')
                for container_code_5th, to_location_code_5th in container_code2location_code_5th.items():
                    print(f'5楼移库任务下发失败: {container_code_5th}--->{to_location_code_5th}')
                raise

            try:
                time.sleep(10)
                self.check_task(container_code2location_code)
                print('\n')
                for container_code_2nd, to_location_code_2nd in container_code2location_code_2nd.items():
                    print(f'2楼移库任务执行成功: {container_code_2nd}--->{to_location_code_2nd}')
                print('\n')
                for container_code_5th, to_location_code_5th in container_code2location_code_5th.items():
                    print(f'5楼移库任务执行成功: {container_code_5th}--->{to_location_code_5th}')

            except Exception:
                print('\n')
                for container_code_2nd, to_location_code_2nd in container_code2location_code_2nd.items():
                    print(f'2楼移库任务执行失败: {container_code_2nd}--->{to_location_code_2nd}')
                print('\n')
                for container_code_5th, to_location_code_5th in container_code2location_code_5th.items():
                    print(f'5楼移库任务执行失败: {container_code_5th}--->{to_location_code_5th}')
                return

        print('\n循环移库执行完成')


if __name__ == '__main__':
    ip = '172.20.110.51'
    port = 19000
    cycle_count = 10  # 循环次数
    floor2count = {'2nd_floor': 5, '5th_floor': 5}  # 2楼和5楼各多少个容器 (>=0)
    EsCycle(ip, port).cycle_move(cycle_count, floor2count)
